#include "trans.h"
#include <inttypes.h>  // 用于跨平台类型打印


/**
 * 从客户端接收文件（支持断点续传）
 * @param usrInfo 包含网络文件描述符的用户信息结构体
 * @param hash 文件名（用哈希值命名）
 * @param totalSize 输出参数，返回文件总大小
 * @return 成功返回接收的总字节数，失败返回-1
 */
int recvFileFromClient(usrInfo_t *usrInfo, char *hash, int *totalSize) {
    // 1. 接收文件总大小
    off_t fileTotalSize = 0;
    ssize_t recvRet = recv(usrInfo->netFd, &fileTotalSize, sizeof(off_t), MSG_WAITALL);
    printf("文件大小（类型长度：%zu 字节，值：%jd）\n", 
           sizeof(off_t), (intmax_t)fileTotalSize);

    if (recvRet != sizeof(off_t)) {
        printf("接收文件大小失败（实际接收：%zd 字节）\n", recvRet);
        return -1;
    }
    *totalSize = fileTotalSize;  // 输出总大小


    // 2. 处理断点续传记录文件（.rcd后缀）
    char recordPath[256] = {0};
    snprintf(recordPath, sizeof(recordPath), "%s.rcd", hash);  // 记录文件路径

    int recordFd;  // 记录文件描述符：存储已接收的字节数
    if (access(recordPath, F_OK) == 0) {
        // 记录文件已存在，直接打开
        recordFd = open(recordPath, O_RDWR);
        printf("检测到已存在的记录文件：%s\n", recordPath);
    } else {
        // 记录文件不存在，创建新文件
        recordFd = open(recordPath, O_RDWR | O_CREAT, 0666);
        printf("创建新的记录文件：%s\n", recordPath);
    }
    ERROR_CHECK(recordFd, -1, "打开记录文件失败");


    // 3. 读取已接收的字节数（断点位置）
    off_t receivedLen = 0;  // 已接收的总字节数（改用off_t避免大文件溢出）
    ssize_t readRet = read(recordFd, &receivedLen, sizeof(receivedLen));
    printf("记录文件中已接收的字节数：%jd（读取结果：%zd）\n", 
           (intmax_t)receivedLen, readRet);


    // 4. 打开目标文件（用hash命名）
    int targetFd = open(hash, O_RDWR | O_CREAT | O_TRUNC, 0666);  // 始终创建新文件，断点续传通过偏移量处理
    if (targetFd == -1) {
        perror("打开目标文件失败");
        close(recordFd);
        return -1;
    }


    // 5. 告知客户端当前断点位置（用于续传）
    if (readRet == 0) {
        // 无历史记录，从头传输
        printf("无历史记录，从头传输\n");
        send(usrInfo->netFd, &receivedLen, sizeof(receivedLen), 0);
        printf("发送断点位置：%jd（字节）\n", (intmax_t)receivedLen);
    } else {
        // 有历史记录，从断点续传
        printf("存在历史记录，从断点续传（已接收：%jd 字节）\n", (intmax_t)receivedLen);
        send(usrInfo->netFd, &receivedLen, sizeof(receivedLen), 0);
        lseek(targetFd, receivedLen, SEEK_SET);  // 移动文件指针到断点
    }


    // 6. 根据文件大小选择接收方式（大文件用mmap，小文件用普通读写）
    off_t lastPrint = 0;  // 上一次打印进度的位置
    const off_t PROGRESS_STEP = fileTotalSize / 10000;  // 进度打印步长（万分之一）

    if (fileTotalSize > 100 * 1024 * 1024) {  // 大文件（>100MB）
        printf("文件大小超过100MB，使用mmap接收\n");

        // 初始化mmap：预先分配文件大小的空间
        if (ftruncate(targetFd, fileTotalSize) == -1) {
            perror("调整文件大小失败");
            close(targetFd);
            close(recordFd);
            return -1;
        }

        char *mapAddr = (char *)mmap(NULL, fileTotalSize, 
                                    PROT_READ | PROT_WRITE, 
                                    MAP_SHARED, targetFd, 0);
        ERROR_CHECK(mapAddr, MAP_FAILED, "mmap失败");

        // 接收数据主循环
        char *currentPtr = mapAddr + receivedLen;  // 当前写入位置（支持续传）
        while (receivedLen < fileTotalSize) {
            // 计算本次需要接收的字节数（不超过1MB）
            off_t remaining = fileTotalSize - receivedLen;
            size_t toRead = (remaining < 1024 * 1024) ? remaining : 1024 * 1024;

            // 接收数据
            ssize_t recvBytes = recv(usrInfo->netFd, currentPtr, toRead, MSG_WAITALL);
            if (recvBytes == -1) {
                perror("接收数据失败");
                munmap(mapAddr, fileTotalSize);
                close(targetFd);
                close(recordFd);
                return -1;
            } else if (recvBytes == 0) {
                printf("连接意外关闭，文件未接收完整（已接收：%jd/%jd）\n", 
                       (intmax_t)receivedLen, (intmax_t)fileTotalSize);
                break;
            }

            // 更新已接收长度和写入指针
            receivedLen += recvBytes;
            currentPtr += recvBytes;

            // 更新记录文件（持久化已接收长度）
            lseek(recordFd, 0, SEEK_SET);
            write(recordFd, &receivedLen, sizeof(receivedLen));
            if (fsync(recordFd) != 0) {
                perror("刷新记录文件缓冲区失败");
                munmap(mapAddr, fileTotalSize);
                close(targetFd);
                close(recordFd);
                return -1;
            }

            // 打印进度（每万分之一或完成时）
            if (receivedLen - lastPrint >= PROGRESS_STEP || receivedLen == fileTotalSize) {
                printf("\r进度:%.2f%%", 100.0 * receivedLen / fileTotalSize);
                fflush(stdout);
                lastPrint = receivedLen;
            }
        }

        // 同步mmap数据到磁盘
        if (msync(mapAddr, fileTotalSize, MS_SYNC) == -1) {
            perror("mmap数据同步到磁盘失败");
        }

        printf("\n接收完成，总接收字节数：%jd\n", (intmax_t)receivedLen);
        munmap(mapAddr, fileTotalSize);  // 解除映射
        close(targetFd);
        close(recordFd);
        return receivedLen;

    } else {  // 小文件（<=100MB）
        printf("文件大小<=100MB，使用普通读写接收\n");

        char buf[4096] = {0};  // 接收缓冲区
        off_t remaining = fileTotalSize - receivedLen;  // 剩余需接收的字节数

        while (receivedLen < fileTotalSize) {
            // 计算本次接收的字节数（不超过1MB）
            size_t toRead = (remaining < 1024 * 1024) ? remaining : 1024 * 1024;

            // 接收数据
            ssize_t recvBytes = recv(usrInfo->netFd, buf, toRead, MSG_WAITALL);
            if (recvBytes == -1) {
                perror("接收数据失败");
                close(targetFd);
                close(recordFd);
                return -1;
            } else if (recvBytes == 0) {
                printf("连接意外关闭，文件未接收完整（已接收：%jd/%jd）\n", 
                       (intmax_t)receivedLen, (intmax_t)fileTotalSize);
                break;
            }

            // 写入目标文件
            ssize_t writtenBytes = write(targetFd, buf, recvBytes);
            if (writtenBytes != recvBytes) {
                perror("写入目标文件失败");
                close(targetFd);
                close(recordFd);
                return -1;
            }

            // 更新状态
            receivedLen += writtenBytes;
            remaining -= writtenBytes;

            // 更新记录文件
            lseek(recordFd, 0, SEEK_SET);
            write(recordFd, &receivedLen, sizeof(receivedLen));
            if (fsync(recordFd) != 0) {
                perror("刷新记录文件缓冲区失败");
                close(targetFd);
                close(recordFd);
                return -1;
            }

            // 打印进度
            if (receivedLen - lastPrint >= PROGRESS_STEP || receivedLen == fileTotalSize) {
                printf("\r进度:%.2f%%", 100.0 * receivedLen / fileTotalSize);
                fflush(stdout);
                lastPrint = receivedLen;
            }
        }

        printf("\n接收完成，总接收字节数：%jd\n", (intmax_t)receivedLen);
        close(targetFd);
        close(recordFd);

        // 校验是否完整接收
        if (receivedLen != fileTotalSize) {
            printf("警告：接收字节数与文件大小不匹配（期望：%jd，实际：%jd）\n", 
                   (intmax_t)fileTotalSize, (intmax_t)receivedLen);
            return -1;
        }
        return receivedLen;
    }
}


/**
 * 向客户端发送文件（支持断点续传）
 * @param usrInfo 包含网络文件描述符的用户信息结构体
 * @param hash 文件名（用哈希值命名）
 * @return 成功返回0，失败返回-1
 */
int sendFileToClient(usrInfo_t *usrInfo, char *hash) {
    printf("待发送文件：%s\n", hash);

    // 1. 获取文件信息（大小）
    struct stat fileStat;
    int targetFd = open(hash, O_RDONLY);  // 只读打开（发送无需写权限）
    ERROR_CHECK(targetFd, -1, "打开待发送文件失败");

    if (fstat(targetFd, &fileStat) == -1) {
        perror("获取文件信息失败");
        close(targetFd);
        return -1;
    }
    off_t fileTotalSize = fileStat.st_size;

    // 发送文件总大小
    if (send(usrInfo->netFd, &fileTotalSize, sizeof(off_t), 0) != sizeof(off_t)) {
        perror("发送文件大小失败");
        close(targetFd);
        return -1;
    }
    printf("已发送文件大小：%jd 字节\n", (intmax_t)fileTotalSize);


    // 2. 接收客户端的断点位置（已接收的字节数）
    off_t sendOffset = 0;  // 已发送的偏移量（断点位置）
    ssize_t recvRet = recv(usrInfo->netFd, &sendOffset, sizeof(sendOffset), MSG_WAITALL);
    if (recvRet != sizeof(sendOffset)) {
        printf("接收断点位置失败（实际接收：%zd 字节）\n", recvRet);
        close(targetFd);
        return -1;
    }
    printf("客户端已接收的字节数：%jd，从该位置续传\n", (intmax_t)sendOffset);


    // 3. 循环发送文件内容（使用sendfile高效传输）
    const size_t SEND_CHUNK = 4096;  // 每次发送4KB
    const off_t PROGRESS_STEP = fileTotalSize / 10000;  // 进度打印步长（万分之一）
    off_t lastPrint = 0;  // 上一次打印进度的位置

    while (sendOffset < fileTotalSize) {
        // 发送数据（每次最多SEND_CHUNK字节）
        ssize_t sentBytes = sendfile(usrInfo->netFd, targetFd, &sendOffset, SEND_CHUNK);
        if (sentBytes == -1) {
            perror("sendfile发送失败");
            close(targetFd);
            return -1;
        } else if (sentBytes == 0) {
            printf("发送中断，当前已发送：%jd 字节\n", (intmax_t)sendOffset);
            break;
        }

        // 打印进度（每万分之一或完成时）
        if (sendOffset - lastPrint >= PROGRESS_STEP || sendOffset == fileTotalSize) {
            printf("\r进度:%.2f%%", 100.0 * sendOffset / fileTotalSize);
            fflush(stdout);
            lastPrint = sendOffset;
        }
    }

    printf("\n发送完成，总发送字节数：%jd\n", (intmax_t)sendOffset);
    close(targetFd);

    // 校验是否完整发送
    if (sendOffset != fileTotalSize) {
        printf("警告：发送字节数与文件大小不匹配（期望：%jd，实际：%jd）\n", 
               (intmax_t)fileTotalSize, (intmax_t)sendOffset);
        return -1;
    }
    return 0;
}
